// OTIMIZADO /** * Vidget - Widget de vídeo interativo * Versão 2.0 */ console.log('%cVidget%cV2.0', 'padding:1px 3px; color: lightgreen; background-color: black', 'padding:1px 3px 1px 0px; color: orange; background-color: black'); // Configurações globais const VIDGET_CONFIG = { STORAGE_URL: 'https://phxkpzehxsbteunlmdoq.supabase.co/storage/v1/object/public/vidget::videos', SUPABASE_URL: 'https://phxkpzehxsbteunlmdoq.supabase.co', SUPABASE_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBoeGtwemVoeHNidGV1bmxtZG9xIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDMxNjg2MDcsImV4cCI6MjAxODc0NDYwN30.oEfBNB49pgdWuGIDYoZf78J9nzZyzrfwZ6cX_OPOF3A', UPDATE_INTERVAL: 5, // Intervalo para atualização de métricas (segundos) SWIPE_THRESHOLD: 50, // Limite para detecção de swipe MOBILE_BREAKPOINT: 768, // Breakpoint para dispositivos móveis METRICS_BUCKETS: { LOW: "0-25", MID_LOW: "26-50", MID_HIGH: "51-75", HIGH: "76-100" }, HIDE_TIMEOUT: 20000, // 20 segundos para reexibir o widget ICONS: { CLOSE: 'https://app.vidget.com.br/assets/public/i-close-icon-2.svg', CLOSE_MOBILE: 'https://app.vidget.com.br/assets/public/i-close-icon-mob.svg', SOUND: 'https://app.vidget.com.br/assets/public/i-sound.svg', SOUND_MOBILE: 'https://app.vidget.com.br/assets/public/i-sound-mob.svg', MUTE: 'https://app.vidget.com.br/assets/public/i-mute.svg', MUTE_MOBILE: 'https://app.vidget.com.br/assets/public/i-mute-mob.svg', PREV: 'https://app.vidget.com.br/assets/public/i-prev-arrow-2.svg', NEXT: 'https://app.vidget.com.br/assets/public/i-next-arrow-2.svg', SHARE: 'https://app.vidget.com.br/assets/public/i-whats-white.svg', COPY_LINK: 'https://app.vidget.com.br/assets/public/i-share-white.svg', COPY: 'https://app.vidget.com.br/assets/public/i-copy-blue.svg', WHATSAPP: 'https://app.vidget.com.br/assets/public/i-whatsapp.svg' }, ASSETS_BASE: 'https://app.vidget.com.br/assets/public/' }; // Estado global let globalSupabaseInstance = null; let videoSet = []; let currentVideoIndex = 0; let currentVideoElement = null; let lastTimeUpdate = 0; let isChangingVideo = false; let arrowsClicked = false; let isExpanded = true; let isDragging = false; let isProcessingSwipe = false; let toggleLock = false; let touchStartY = 0; let lastTouchY = 0; let startTime = null; let dragStartTime = null; let vidgetContainer; /** * Funções de utilidade (Utility Functions) */ const VidgetUtils = { /** * Adiciona estilo CSS dinamicamente * @param {string} cssString String CSS a ser adicionada */ injectCSS: function (cssString) { const style = document.createElement('style'); style.textContent = cssString; document.head.appendChild(style); console.log('CSS injetado:', cssString.substring(0, 50) + '...'); }, /** * Adiciona Google Fonts dinamicamente */ addGoogleFonts: function () { const fontLink = document.createElement('link'); fontLink.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'; fontLink.rel = 'stylesheet'; document.head.appendChild(fontLink); }, /** * Normaliza URL removendo www e padronizando formatação * @param {string} url URL para normalizar * @returns {string} URL normalizada */ normalizeUrlRemoveWWW: function (url) { if (!url) return ''; // Se não tiver protocolo, adiciona https if (!url.startsWith('http://') && !url.startsWith('https://')) { url = 'https://' + url; } try { let urlObj = new URL(url); // Remove 'www.' let hostname = urlObj.hostname.replace(/^www\./, ''); let normalizedUrl = urlObj.protocol + '//' + hostname + urlObj.pathname; // Remove barra final e deixa tudo minúsculo normalizedUrl = normalizedUrl.replace(/\/$/, '').toLowerCase(); // Reinsere query se for /busca ou tiver variant_id if (urlObj.pathname === '/busca') { normalizedUrl += urlObj.search; } else if (urlObj.searchParams.has('variant_id')) { let variantId = urlObj.searchParams.get('variant_id'); normalizedUrl += '?variant_id=' + variantId; } return normalizedUrl; } catch (error) { console.error('Erro ao normalizar URL (removeWWW):', error); return url.toLowerCase(); } }, /** * Normaliza URL mantendo www e padronizando formatação * @param {string} url URL para normalizar * @returns {string} URL normalizada */ normalizeUrlKeepWWW: function (url) { if (!url) return ''; // Se não tiver protocolo, adiciona https if (!url.startsWith('http://') && !url.startsWith('https://')) { url = 'https://' + url; } try { let urlObj = new URL(url); // Mantém o www no hostname let normalizedUrl = urlObj.origin + urlObj.pathname; normalizedUrl = normalizedUrl.replace(/\/$/, '').toLowerCase(); if (urlObj.pathname === '/busca') { normalizedUrl += urlObj.search; } else if (urlObj.searchParams.has('variant_id')) { let variantId = urlObj.searchParams.get('variant_id'); normalizedUrl += '?variant_id=' + variantId; } return normalizedUrl; } catch (error) { console.error('Erro ao normalizar URL (keepWWW):', error); return url.toLowerCase(); } }, /** * Converte texto em HTML com links clicáveis * @param {string} text Texto para converter * @returns {string} HTML com links */ convertTextToLinks: function (text) { if (!text) return ''; const urlRegex = /(\bhttps?:\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]|(?:\b[a-z][a-z0-9-]*\.)+[a-z]{2,}\/[-A-Z0-9+&@#\/%=~_|!:,.;]*\/?)/ig; return text.replace(urlRegex, function (url) { if (!/^https?:\/\//i.test(url)) { return `${url}`; } const displayUrl = url.replace(/^https?:\/\//, ''); return `${displayUrl}`; }); }, /** * Verifica se o elemento possui box-shadow * @param {HTMLElement} element Elemento para verificar * @returns {boolean} Se possui box-shadow */ hasBoxShadow: function (element) { const style = window.getComputedStyle(element); return style.boxShadow !== 'none' && style.boxShadow !== ''; }, /** * Obtém o bucket de conclusão com base na porcentagem * @param {number} completionPercentage Porcentagem de conclusão * @returns {string} Bucket de conclusão */ getCompletionBucket: function (completionPercentage) { if (completionPercentage <= 25) return VIDGET_CONFIG.METRICS_BUCKETS.LOW; if (completionPercentage <= 50) return VIDGET_CONFIG.METRICS_BUCKETS.MID_LOW; if (completionPercentage <= 75) return VIDGET_CONFIG.METRICS_BUCKETS.MID_HIGH; return VIDGET_CONFIG.METRICS_BUCKETS.HIGH; }, /** * Pré-carrega vídeos adjacentes para melhorar a experiência * @param {number} index Índice do vídeo atual */ preloadAdjacentVideos: function (index) { if (!videoSet || videoSet.length <= 1) return; // Pré-carrega todos os vídeos, não apenas adjacentes for (let i = 0; i < videoSet.length; i++) { if (i !== index) { // Não recarregue o atual const video = document.createElement('video'); video.src = `${VIDGET_CONFIG.STORAGE_URL}/${videoSet[i].url}`; video.preload = 'auto'; video.style.display = 'none'; document.body.appendChild(video); // Força o carregamento video.load(); // Remove depois de pré-carregar setTimeout(() => { if (document.body.contains(video)) { document.body.removeChild(video); } }, 5000); } } } }; /** * Estilos CSS para o Vidget */ const VidgetStyles = { baseCSS: ` #vidget__container { width: 24vw; height: calc(22vw* 1.7692); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#000000",endColorstr="#000000",GradientType=1); position: fixed; z-index: 999999999; font-family: 'Inter', sans-serif; font-size: 16px; bottom: 20px; overflow: hidden; background: rgba(0, 0, 0, 0.35); transition: opacity 0.3s ease-in-out; visibility: hidden; /* Inicialmente oculto */ } #vidget__container.loaded.shadow-applied.minimized { border-radius: 50% !important; } #vidget__container.shadow-applied.loaded:not(.minimized) { border-radius: 1rem !important; } #vidget__container.loaded { opacity: 1; pointer-events: auto; visibility: visible; } #vidget__container.loading { opacity: 0; pointer-events: none; visibility: hidden; } #vidget__container.minimized.shadow-applied.minimized.size-p { width: 6vw !important; height: 6vw !important; } #vidget__container.minimized.shadow-applied.minimized.size-m { width: 8vw !important; height: 8vw !important; } #vidget__container.minimized.shadow-applied.minimized.size-g { width: 12vw !important; height: 12vw !important; } @media screen and (max-width: 768px) { #vidget__container.minimized.shadow-applied.minimized.size-p, #vidget__container.minimized.shadow-applied.minimized.size-m, #vidget__container.minimized.shadow-applied.minimized.size-g { width: 20vw !important; height: 20vw !important; } #vidget__container.minimized.size-g video, #vidget__container.minimized.size-m video, #vidget__container.minimized.size-p video { width: 20vw !important; height: 20vw !important; } } #vidget__container.minimized.size-g video { width: 12vw ; height: 12vw ; } #vidget__container.minimized .vidget__progress-container { display: none; } #vidget__container.minimized #share-button { display: none !important; } .article-content { display: flex; gap: .65rem; } .btn-close { padding: 0; z-index: 999; } .full-mode-article p:first-of-type { margin-top: 10px !important; } .full-mode-article p { margin-bottom: 20px !important; } #vidget__container article p { font-family: 'Inter', sans-serif !important; } #vidget__container.no-border article { margin-left: 3px !important; } @media screen and (max-width: 768px) { #vidget__container { width: 100% !important; height: 100% !important; margin-left: 0px; border-radius: 0 !important; z-index: 99999999999; bottom: 0px; } #vidget__container:not(.minimized) { bottom: 0px !important; } #vidget__container.minimized.shadow-applied.minimized { width: 18vw !important; height: 18vw !important; } #vidget__container.minimized { bottom: 20px; } #vidget__container.minimized video { top: 0px !important; } #vidget__container article div a[data-vidget-url] { width: calc(100% - 10vw - 235px) !important; font-size: .8em !important; bottom: 4px !important; padding: 3px 3px !important; } #vidget__container article { width: 100% !important; height: fit-content !important; bottom: 0 !important; margin: 10px 0px 4px 0px !important; } @media screen and (max-width: 768px) { #vidget__container article{ width: 95% !important; height: fit-content !important; bottom: 0 !important; margin: 10px 0px 10px 10px !important; } } #vidget__container article p { line-height: 16px !important; font-size: .9em !important; font-family: 'Inter', sans-serif !important; padding-top: .5rem !important; } .article-content { gap: 1rem; } #vidget__container.minimized .vidget__video-container { width: 19vw !important; height: 19vw !important; border-radius: 50% !important; } #vidget__container.minimized .vidget__video-container.no-box-shadow { width: 18vw; height: 18vw; } #vidget__container.minimized video, #vidget__container.minimized div[data-vidget-video-overlay] { width: 20vw !important; height: 20vw !important; } #vidget__container.minimized video { border-radius: 50% !important; } #vidget__container.minimized { width: 20vw !important; height: 20vw !important; box-shadow: none; } } `, progressBarCSS: ` .vidget__progress-container { position: absolute; top: 6px; left: 0; z-index: 99999; width: 90%; height: 6px; display: flex; margin-left: 15px; gap: 4px; /* Espaço entre as barras */ } .vidget__progress-bar { height: 6px; flex: 1; /* Distribui o espaço igualmente */ background-color: rgba(255, 255, 255, 0.3); /* Cor inativa */ border-radius: 2rem; overflow: hidden; /* Para conter a barra de progresso */ position: relative; } .vidget__progress-bar.active { background-color: rgba(255, 255, 255, 0.9); } .vidget__progress-bar.completed { background-color: rgba(255, 255, 255, 0.9); } `, controlsCSS: ` #vidget__container article { width: 95%; height: fit-content; bottom: 0; margin: 10px auto; border-radius: 5px; display: flex; } #vidget__container.no-border article { width: 100%; } .vidget__video-container { transition: transform 0.5s ease-in-out; height: 100%; width: 100%; } #vidget__container.minimized .vidget__video-container { height: auto; } .video-fade-out { opacity: 0; transition: opacity 0.5s ease-out; } .video-fade-in { opacity: 1; transition: opacity 0.5s ease-in; } #vidget__container.minimized video { width: 9vw; height: 9vw; } #vidget__container.minimized div[data-vidget-video-overlay] { width: 8.5vw; height: 8.5vw; } #vidget__container.minimized article, #vidget__container.minimized button { display: none !important; } div[data-vidget-video-overlay] { width: 100%; height: 102%; } #vidget__container video { width: 100%; border-radius: 0.75rem; object-fit: cover; height: 100%; width: 100%; top: 0; } @media screen and (max-width: 768px) { #vidget__container video { border-radius: 0; } } #vidget__container video.with-border { max-width: 100%; width: 100%; height: 95vh; max-height: 100%; border-radius: 0.75rem; object-fit: cover; top: 0px; } #vidget__container article { position: absolute; background: rgba(21, 21, 21, 0.62); backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); z-index: 99999999; } #vidget__video-overlay > article > div > div > div > p.mb-2 { font-weight: 700 !important; font-size: 1.25em; text-wrap: nowrap; max-width: 100%; text-overflow: ellipsis; overflow: hidden; } #vidget__container button { position: absolute; } .btn-close { cursor: pointer; } .btn-close img { position: relative; width: 100%; } .btn-close.vidget-btn-close { background: url('${VIDGET_CONFIG.ICONS.CLOSE}') no-repeat center, /* imagem */ #15151575 !important; /* cor de fundo com transparência */ backdrop-filter: blur(5px); border-radius: 50%; padding: .75rem; top: 20px !important; } #vidget__container article img { width: 35% !important; max-width: 100px; border-radius: 8px; height: 100px; object-fit: cover; } #vidget__container article div { padding: 10px 12px; line-height: 18px; color: #fff; } #vidget__container article div > * { margin: 0; padding: 0; width: 100%; font-family: 'Inter', sans-serif; text-wrap: nowrap; max-width: 100%; text-overflow: ellipsis; overflow: hidden; } #vidget__container article div p[data-vidget-price] { font-size: 0.8em; font-family: 'Inter', sans-serif; } #vidget__container article div a[data-vidget-url] { position: absolute; bottom: 8px; width: calc(100% - 10vw + -100px); color: #fff; padding: 6px 8px; border-radius: 3px; font-size: 0.75em; font-weight: 700; text-align: center; text-decoration: none; text-transform: uppercase; letter-spacing: 1px; right: 8px } #vidget__container article div a[data-vidget-url]:hover { opacity: 1; } #vidget__container button { top: 10px; right: -5px; border: none; width: 40px; height: 40px; } .navigation-arrows { position: absolute; top: 50%; width: calc(100% - 10px); z-index: 999999; display: flex; flex-direction: column; } .prev-arrow, .next-arrow { background-color: transparent; border: none; font-size: 24px; color: white; cursor: pointer; padding: 0; position: absolute; top: 50%; transform: translateY(-50%); } .prev-arrow { top: 30px !important; transform: rotate(-0deg); } .next-arrow { top: -40px !important; transform: rotate(-0deg); } .navigation-arrows img { pointer-events: none; } .prev-arrow { background: url('${VIDGET_CONFIG.ICONS.PREV}') no-repeat center / contain, /* ajusta o ícone */ #15151575; /* cor de fundo com transparência */ backdrop-filter: blur(5px); border-radius: 50%; top: 10px !important; background-size: 50%; } .next-arrow { background: url('${VIDGET_CONFIG.ICONS.NEXT}') no-repeat center / contain, /* ajusta o ícone */ #15151575; /* cor de fundo com transparência */ backdrop-filter: blur(5px); border-radius: 50%; background-size: 50%; } #sound-on { background: url('${VIDGET_CONFIG.ICONS.SOUND}') no-repeat center, #15151575; backdrop-filter: blur(5px); right: 5px !important; border-radius: 50%; padding: 1rem; top: 67.5% !important; cursor: pointer; background-size: 100%; z-index: 999999; } #copy-link-button { background: url(${VIDGET_CONFIG.ICONS.COPY_LINK}) no-repeat center, #15151575; right: 6px !important; border-radius: 50%; padding: .75rem; top: 110px !important; cursor: pointer; z-index: 999999; } #share-button { background: url(${VIDGET_CONFIG.ICONS.SHARE}) no-repeat center, #15151575; right: 6px !important; border-radius: 50%; padding: .75rem; top: 160px !important; cursor: pointer; z-index: 999999; } #sound-off { background: url('${VIDGET_CONFIG.ICONS.MUTE}') no-repeat center, #15151575; backdrop-filter: blur(5px); right: 5px !important; border-radius: 50%; padding: 1rem; top: 67.5% !important; cursor: pointer; background-size: 110%; z-index: 999999; } #video-slider { overflow: hidden; position: relative; height: 100%; } .video-item { position: absolute; width: 100%; height: 100%; transition: transform 0.5s ease-in-out; } .video-item { -webkit-transform: translate3d(0,0,0); transform: translate3d(0,0,0); -webkit-backface-visibility: hidden; backface-visibility: hidden; -webkit-perspective: 1000; perspective: 1000; } .btn-close { right: 5px !important; background: transparent; } @media screen and (max-width: 768px) { .prev-arrow, .next-arrow { font-size: 20px; } .prev-arrow, .next-arrow { padding: 1.25rem } .next-arrow { top: -75px !important; } #sound-on { background: url('${VIDGET_CONFIG.ICONS.SOUND_MOBILE}') no-repeat center; top: 75% !important; padding: 1.5rem; right: 5px !important; background-size: 100% !important; } #sound-off { background: url('${VIDGET_CONFIG.ICONS.MUTE_MOBILE}') no-repeat center; padding: 1.5rem; right: 5px !important; top: 75% !important; background-size: 100% !important; } .btn-close.vidget-btn-close { background: url('${VIDGET_CONFIG.ICONS.CLOSE_MOBILE}') no-repeat center !important; padding: 1.5rem; border-radius: 50%; background-size: 100% !important; } #vidget__container video { height: 100vh; } #vidget__container video.with-border { height: 100vh; } #vidget__container article img { width: 35% !important; max-width: 100%; border-top-left-radius: 5px; border-bottom-left-radius: 5px; object-fit: cover; } #vidget__container article div { padding: 10px; line-height: 18px; color: #fff; } #vidget__container article div > * { margin: 0; padding: 0; width: 100%; } } `, playButtonCSS: ` #vidget-play-button { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 60px; height: 60px; background: rgba(0, 0, 0, 0.5); border: none; border-radius: 50%; cursor: pointer; z-index: 999999; display: none; backdrop-filter: blur(5px); transition: all 0.3s ease; margin-top: 75% } #vidget-play-button:hover { background: rgba(0, 0, 0, 0.7); transform: translate(-50%, -50%) scale(1.1); } #vidget-play-button::before { content: ''; position: absolute; top: 50%; left: 55%; transform: translate(-50%, -50%); width: 0; height: 0; border-style: solid; border-width: 12px 0 12px 20px; border-color: transparent transparent transparent #ffffff; } #vidget__container.minimized #vidget-play-button { width: 40px; height: 40px; display: none !important; } #vidget__container.minimized #vidget-play-button::before { border-width: 8px 0 8px 14px; } @media screen and (max-width: 768px) { #vidget-play-button { margin-top: 100% } } `, additionalCSS: ` .vidget__video-container { position: relative; overflow: hidden; } .vidget__video-container video { position: absolute; width: 100%; height: 100%; transition: top 1.6s ease-out; } `, /** * Inicializa todos os estilos CSS */ initStyles: function () { VidgetUtils.addGoogleFonts(); VidgetUtils.injectCSS(this.baseCSS); VidgetUtils.injectCSS(this.progressBarCSS); VidgetUtils.injectCSS(this.controlsCSS); VidgetUtils.injectCSS(this.playButtonCSS); VidgetUtils.injectCSS(this.additionalCSS); console.log('Todos os estilos inicializados'); } }; // Inicialização e configuração do Vidget function initVidget() { // Injetar estilos CSS VidgetStyles.initStyles(); // Obter o parâmetro de localização da URL do script const vidget__script_url = new URL(document.currentScript.src); const vidget__location_param = decodeURIComponent(vidget__script_url.searchParams.get('location')); // Criar o contêiner principal initContainer(); // Iniciar o Vidget initSupabase((supabaseInstance) => { getVideosByUrl(supabaseInstance, vidget__location_param) .then(response => { if (response && response.length > 0) { videoSet = response; vidgetContainer.innerHTML = ''; initializeVideoOverlay(supabaseInstance, response); VidgetUtils.preloadAdjacentVideos(0); } else { vidgetContainer.style.display = 'none'; } }) .catch(error => { console.error('Erro durante a inicialização de vídeos:', error); }); }); // Listeners para salvar métricas quando a página for fechada window.addEventListener('beforeunload', async (event) => { if (currentVideoElement) { await saveMetricsOnInterruption( globalSupabaseInstance, videoSet[currentVideoIndex].id, lastTimeUpdate ); } }); document.addEventListener('visibilitychange', async () => { if (document.hidden && currentVideoElement) { await saveMetricsOnInterruption( globalSupabaseInstance, videoSet[currentVideoIndex].id, lastTimeUpdate ); } }); } // Inicializa o container do Vidget function initContainer() { // Create the container principal do Vidget vidgetContainer = document.createElement('div'); vidgetContainer.id = 'vidget__container'; // MELHORIA: Use opacity em vez de visibility vidgetContainer.style.opacity = '0'; vidgetContainer.style.transition = 'opacity 0.3s ease-in-out'; vidgetContainer.classList.add('minimized'); document.body.appendChild(vidgetContainer); // Create overlay for fullscreen mode const fullScreenOverlay = document.createElement('div'); fullScreenOverlay.id = 'fullScreenOverlay'; fullScreenOverlay.style.position = 'fixed'; fullScreenOverlay.style.top = '0'; fullScreenOverlay.style.left = '0'; fullScreenOverlay.style.width = '100vw'; fullScreenOverlay.style.height = '100vh'; fullScreenOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0.8)'; fullScreenOverlay.style.display = 'none'; fullScreenOverlay.style.zIndex = '999'; document.body.appendChild(fullScreenOverlay); } // Inicializa a conexão com Supabase function initSupabase(callback) { // Adiciona o script do Supabase const supabaseScript = document.createElement('script'); supabaseScript.type = 'text/javascript'; supabaseScript.src = 'https://cdn.jsdelivr.net/npm/@supabase/supabase-js'; document.body.appendChild(supabaseScript); // Quando o script for carregado, cria a instância do Supabase supabaseScript.onload = function () { if (typeof supabase === 'undefined') { console.error('Supabase não está definido.'); return; } globalSupabaseInstance = supabase.createClient( VIDGET_CONFIG.SUPABASE_URL, VIDGET_CONFIG.SUPABASE_KEY ); if (typeof callback === 'function') { callback(globalSupabaseInstance); } }; } // Busca vídeos no Supabase com base na URL async function getVideosByUrl(supabaseInstance, targetUrl) { if (!targetUrl) return null; // 1. Normaliza removendo 'www.' let normalizedUrlNoWWW = VidgetUtils.normalizeUrlRemoveWWW(targetUrl); let videos = await fetchVideosByURL(supabaseInstance, normalizedUrlNoWWW); // 2. Se não encontrar nada, tenta normalização que mantém 'www.' if (!videos || videos.length === 0) { console.log("Nada com URL sem www. Tentando com www..."); let normalizedUrlWithWWW = VidgetUtils.normalizeUrlKeepWWW(targetUrl); videos = await fetchVideosByURL(supabaseInstance, normalizedUrlWithWWW); } // 3. Se ainda assim não encontrou nada, tenta fallback com base original if (!videos || videos.length === 0) { console.log("Ainda não achou. Tentando URL base..."); const urlObject = new URL(targetUrl); let baseUrl = urlObject.origin + urlObject.pathname; videos = await fetchVideosByURL(supabaseInstance, baseUrl); } // 4. Se no final não encontrar nada, retorna null if (!videos || videos.length === 0) { console.log("Nenhum vídeo encontrado para a URL especificada."); return null; } // Continua com o resto (pegar video_set_id, filtrar "active", etc.) let videoSetIds = videos.map(video => video.video_set_id); let uniqueVideoSetIds = [...new Set(videoSetIds)]; let { data: videoSets, error: errorVideoSets } = await supabaseInstance .from('vidget::video_sets') .select(`id, image_url, status`) .in('id', uniqueVideoSetIds); if (errorVideoSets) { console.error('Erro ao buscar conjuntos de vídeos:', errorVideoSets); return null; } let activeVideoSets = videoSets.filter(set => set.status === 'active'); let videosWithImageUrls = videos.map(video => { let videoSet = activeVideoSets.find(set => set.id === video.video_set_id); return { ...video, image_url_from_set: videoSet ? videoSet.image_url : null }; }); return videosWithImageUrls; } // Busca vídeos com base na URL fornecida async function fetchVideosByURL(supabaseInstance, url) { try { // Primeira tentativa: buscar em urlVideoSets (array) let { data: videosFromArray, error: errorFromArray } = await supabaseInstance .from('vidget::videos') .select('title, url, metadata, id, video_set_id') .filter('metadata->urlVideoSets', 'cs', `[{"value": "${url}"}]`); // Se não encontrou em urlVideoSets, tenta urlVideoSet (string) if (!videosFromArray || videosFromArray.length === 0) { console.log('Não encontrou em urlVideoSets, tentando urlVideoSet'); let { data: videosFromSingle, error: errorFromSingle } = await supabaseInstance .from('vidget::videos') .select('title, url, metadata, id, video_set_id') .eq('metadata->>urlVideoSet', url); if (errorFromSingle) { console.error('Erro ao buscar vídeos de urlVideoSet:', errorFromSingle); return null; } return videosFromSingle; } return videosFromArray; } catch (error) { console.error('Erro em fetchVideosByURL:', error); return null; } } // Busca um vídeo específico pelo ID async function getVideoById(supabaseInstance, videoId) { let query = supabaseInstance .from('vidget::videos') .select('title, url, metadata, id') .eq('id', videoId) .single(); const { data, error } = await query; if (error) { console.error('Erro ao buscar vídeo:', error); return null; } return data; } // Cria o markup HTML para o overlay do vídeo function createVideoMarkup(productUrl, metadata = {}, image_url_from_set = null) { console.log("URL da imagem do conjunto de vídeos:", image_url_from_set); // Compatibilidade: verificar estrutura dos metadados const isNewFormat = 'showTitle' in metadata || 'showDescription' in metadata || 'showPrice' in metadata || 'buttonText' in metadata; // Extrair dados de acordo com o formato const { description, productPrice, price, title, productCardPrice, image_url, type, buttonUrl, buttonText, showTitle, showDescription, showPrice, showButton, buttonColor } = metadata; // Gerar título baseado no formato dos metadados const displayTitle = isNewFormat ? (showTitle ? title : '') : title; // Gerar descrição baseada no formato dos metadados const displayDescription = isNewFormat ? (showDescription ? description : '') : description; // Gerar preço baseado no formato dos metadados const displayPrice = isNewFormat ? (showPrice ? (price || productPrice || productCardPrice) : '') : (productPrice || productCardPrice); // URL do botão baseada no formato dos metadados const displayButtonUrl = isNewFormat ? (showButton ? (buttonUrl || productUrl) : '') : productUrl; // Texto do botão baseado no formato dos metadados const displayButtonText = isNewFormat ? (showButton ? (buttonText || 'Ver produto') : '') : 'Ver produto'; // Cor do botão (apenas no novo formato) const buttonStyle = isNewFormat && buttonColor ? `background-color: #${buttonColor}; border-color: #${buttonColor}; color: white;` : ''; // Usar 'image_url_from_set' se disponível; caso contrário, usar 'image_url' ou uma imagem padrão const imageUrl = image_url_from_set || image_url; // Verificar se deve mostrar a imagem (compatibilidade) const shouldShowImage = isNewFormat ? metadata.showThumbnail : (imageUrl && type === 'productCard'); // Construir elemento de imagem const imageDiv = shouldShowImage && imageUrl ? `Produto` : `
`; // Aplica padding ao parágrafo de descrição apenas quando o tipo é 'description' const descriptionStyle = (!isNewFormat && type === 'description') ? 'padding: 0px 12px;' : ''; // Preparar HTML dos detalhes do produto let productDetailsHTML = ''; if (isNewFormat || (!isNewFormat && type !== 'empty-field')) { productDetailsHTML = `
${imageDiv}
${displayTitle ? `

${displayTitle}

` : ''} ${displayDescription ? `

${VidgetUtils.convertTextToLinks(displayDescription)}

` : ''} ${displayPrice ? `

${isNaN(displayPrice) ? displayPrice.includes('R$') ? displayPrice : `R$ ${displayPrice}` : `R$ ${displayPrice}`}

` : ''}
${displayButtonUrl ? `${displayButtonText}` : ''}
`; } // Barras de progresso para múltiplos vídeos const progressBarHTML = `
${videoSet.map(() => `
`).join('')}
`; // Navegação entre vídeos const navigationArrows = ` `; // Retorna o markup final com ou sem
dependendo do tipo return `
${progressBarHTML} ${(isNewFormat || (!isNewFormat && type !== 'empty-field')) ? `
${productDetailsHTML}
` : ''} ${navigationArrows}
`; } // Inicializa as barras de progresso function initializeProgressBar(videoElement, currentIndex) { const progressBars = document.querySelectorAll('.vidget__progress-bar'); // Reset e setup inicial das barras progressBars.forEach((bar, index) => { if (index < currentIndex) { bar.classList.add('completed'); bar.style.width = '100%'; } else if (index > currentIndex) { bar.classList.remove('completed', 'active'); bar.style.width = '0%'; } else { bar.classList.add('active'); bar.style.width = '0%'; } }); const updateProgress = () => { if (!videoElement || videoElement.duration === 0) return; const percent = (videoElement.currentTime / videoElement.duration) * 100; if (progressBars[currentIndex]) { progressBars[currentIndex].style.width = `${percent}%`; } }; videoElement.addEventListener('timeupdate', updateProgress); videoElement.addEventListener('ended', () => { if (progressBars[currentIndex]) { progressBars[currentIndex].classList.add('completed'); progressBars[currentIndex].style.width = '100%'; } if (currentIndex < progressBars.length - 1) { changeVideo(globalSupabaseInstance, 1); } }); } // Aplica estilos de overlay com base nas configurações function applyOverlayStyles(borderColor, positionInput) { const firstVideoSetMetadata = videoSet[0].metadata || {}; // Aplicar o tamanho ANTES de qualquer outra modificação de estilo updateSizeClass(firstVideoSetMetadata); // Adiciona '#' ao `borderColor` se ele estiver presente e não começar com '#' if (borderColor && !borderColor.startsWith('#')) { borderColor = `#${borderColor}`; } // Verifica se o borderColor está vazio ou nulo, e aplica box-shadow se for o caso if (!borderColor) { vidgetContainer.style.boxShadow = '2px 2px 12px 6px rgba(0, 0, 0, 0.25)'; vidgetContainer.style.borderColor = ''; vidgetContainer.style.borderStyle = ''; vidgetContainer.style.borderWidth = ''; } else { vidgetContainer.style.boxShadow = ''; vidgetContainer.style.borderColor = borderColor; vidgetContainer.style.borderStyle = 'solid'; vidgetContainer.style.borderWidth = '4px'; } // vidgetContainer.style.borderRadius = '1rem'; checkAndApplyShadowClass(); let positions; // Prioridade: 'position' > 'overlayPosition' if (firstVideoSetMetadata.position) { console.log('Usando position'); positions = firstVideoSetMetadata.position.split(';').reduce((acc, curr) => { const [key, value] = curr.split(':'); if (key && value) { acc[key.trim()] = value.trim(); } return acc; }, {}); } else if (firstVideoSetMetadata.overlayPosition) { console.log('Usando overlayPosition'); positions = firstVideoSetMetadata.overlayPosition.split(';').reduce((acc, curr) => { const [key, value] = curr.split(':'); if (key && value) { acc[key.trim()] = value.trim(); } return acc; }, {}); } else { console.log('Sem posição definida'); positions = {}; // Sem posição definida } console.log('Posições processadas:', positions); // Aplica posições (como left, right, bottom) ao vidgetContainer Object.keys(positions).forEach(key => { const value = positions[key]; if (value) { vidgetContainer.style[key] = value; } }); // Configura botão de WhatsApp usando o primeiro objeto do conjunto const shareButton = document.getElementById('share-button'); if (shareButton) { const whatsappButton = firstVideoSetMetadata.whatsappButton; console.log('WhatsApp Button:', whatsappButton); shareButton.style.display = whatsappButton ? 'block' : 'none'; } } function updateSizeClass(metadata) { if (!metadata) return; const size = metadata.size || 'M'; // Forçar a remoção de todas as classes de tamanho vidgetContainer.classList.remove('size-p', 'size-m', 'size-g'); // Adicionar a classe de tamanho com um pequeno delay para garantir que outras modificações de estilo não interfiram setTimeout(() => { switch (size.toLowerCase()) { case 'p': vidgetContainer.classList.add('size-p'); break; case 'm': vidgetContainer.classList.add('size-m'); break; case 'g': vidgetContainer.classList.add('size-g'); break; default: vidgetContainer.classList.add('size-m'); } console.log('Classe de tamanho aplicada:', size.toLowerCase()); }, 0); } function applyMinimizedStyles() { if (!vidgetContainer) return; const firstVideoSetMetadata = videoSet[0].metadata || {}; const size = firstVideoSetMetadata.size || 'M'; console.log('Tamanho para minimizado:', size); // Remove todas as classes de tamanho vidgetContainer.classList.remove('size-p', 'size-m', 'size-g'); // Adiciona a classe de tamanho correta switch (size.toLowerCase()) { case 'p': vidgetContainer.classList.add('size-p'); break; case 'm': vidgetContainer.classList.add('size-m'); break; case 'g': vidgetContainer.classList.add('size-g'); break; default: vidgetContainer.classList.add('size-m'); } // Resto do código de minimização permanece igual const videoContainer = vidgetContainer.querySelector('.vidget__video-container'); vidgetContainer.style.width = '8vw'; vidgetContainer.style.height = '8vw'; vidgetContainer.style.borderRadius = '50%'; if (videoContainer) { videoContainer.style.width = '100%'; videoContainer.style.height = '100%'; if (videoContainer.querySelector('video')) { videoContainer.querySelector('video').muted = true; } } // Hide article and buttons in minimized state const article = vidgetContainer.querySelector('article'); if (article) { article.style.display = 'none'; } // Hide progress bar toggleProgressBarVisibility(false); } // Verifica e aplica classe de sombra function checkAndApplyShadowClass() { const boxShadowStyle = window.getComputedStyle(vidgetContainer).boxShadow; const borderColorStyle = vidgetContainer.style.borderColor; // Aplica shadow-applied se houver box-shadow OU borderColor if (boxShadowStyle !== 'none' || borderColorStyle !== '') { vidgetContainer.classList.add('shadow-applied'); } else { vidgetContainer.classList.remove('shadow-applied'); } // Garantir que o tamanho seja preservado após alterações em shadow/border if (videoSet && videoSet.length > 0) { updateSizeClass(videoSet[0].metadata || {}); } } // Cria o elemento de vídeo function createVideoPlayer(container, videoSrc) { const video = document.createElement('video'); video.setAttribute('playsinline', ''); video.setAttribute('webkit-playsinline', ''); // Importante para iOS video.setAttribute('x-webkit-airplay', 'allow'); video.setAttribute('preload', 'auto'); // iOS prefere MP4 video.setAttribute('type', 'video/mp4'); video.autoplay = true; video.muted = true; video.loop = true; // Específico para iOS if (/iPad|iPhone|iPod/.test(navigator.userAgent)) { video.load(); // Força load no iOS } return video; } function updateSoundIcons(isMuted) { const soundOnButton = document.querySelector('#sound-on'); const soundOffButton = document.querySelector('#sound-off'); const isMobile = window.innerWidth < VIDGET_CONFIG.MOBILE_BREAKPOINT; console.log('Updating sound icons:', { isMuted, isMobile, currentVideoMuted: currentVideoElement ? currentVideoElement.muted : 'no video' }); if (soundOnButton && soundOffButton) { if (isMobile) { // Para mobile: // - soundOnButton representa SOM DESLIGADO // - soundOffButton representa SOM LIGADO if (isMuted) { // Quando isMuted é true, significa som desligado soundOnButton.style.display = 'block'; // Mostra ícone de som desligado soundOffButton.style.display = 'none'; // Esconde ícone de som ligado } else { // Quando isMuted é false, significa som ligado soundOnButton.style.display = 'none'; // Esconde ícone de som desligado soundOffButton.style.display = 'block'; // Mostra ícone de som ligado } } else { // Lógica padrão para desktop if (isMuted) { soundOffButton.style.display = 'block'; soundOnButton.style.display = 'none'; } else { soundOffButton.style.display = 'none'; soundOnButton.style.display = 'block'; } } } } // Atualiza os estilos de vídeo function updateVideoStyles() { const videoElement = vidgetContainer.querySelector('video'); if (!videoElement) return; // Verifica se borderColor está definido e aplica a classe 'with-border' if (vidgetContainer.style.borderColor) { videoElement.classList.add('with-border'); } else { videoElement.classList.remove('with-border'); } } // Mostra ou esconde a barra de progresso function toggleProgressBarVisibility(show) { const progressBarContainer = document.querySelector('.vidget__progress-container'); if (progressBarContainer) { progressBarContainer.style.display = show ? 'flex' : 'none'; } } // Inicializa o overlay de vídeo async function initializeVideoOverlay(supabaseInstance, videoSet) { try { vidgetContainer.classList.add('loading'); let videoContainer = document.querySelector('.vidget__video-container'); if (!videoContainer) { videoContainer = document.createElement('div'); videoContainer.className = 'vidget__video-container'; vidgetContainer.appendChild(videoContainer); } else { videoContainer.innerHTML = ''; } // Verifica se há vídeos no conjunto if (!videoSet || videoSet.length === 0) { vidgetContainer.classList.remove('loading'); vidgetContainer.style.display = 'none'; return; } // Ordenar o videoSet para garantir que o primeiro seja o primeiro videoSet.sort((a, b) => a.id - b.id); const firstVideo = videoSet[0]; const { overlayColor, overlayPosition, productUrl } = firstVideo.metadata || {}; updateSizeClass(firstVideo.metadata); applyOverlayStyles(overlayColor, overlayPosition); videoContainer.innerHTML = createVideoMarkup(productUrl, firstVideo.metadata, firstVideo.image_url_from_set); attachEventListeners(supabaseInstance, videoSet, videoContainer); attachMouseDragListeners(); await loadVideo(supabaseInstance, videoSet, videoContainer, 0); const shareButton = document.getElementById('share-button'); const metadata = firstVideo.metadata; if (shareButton) { if (metadata && metadata.whatsappButton) { shareButton.style.display = 'block'; } else { shareButton.style.display = 'none'; } } } catch (error) { console.error('Erro ao inicializar o video overlay:', error); vidgetContainer.classList.remove('loading'); vidgetContainer.style.display = 'none'; } } function attachMouseDragListeners() { vidgetContainer.addEventListener('mousedown', (e) => { // Ignora cliques em botões exceto no botão de play if (e.target.tagName === 'BUTTON' && e.target.id !== 'vidget-play-button') return; dragStartTime = new Date().getTime(); // Handle para arrastar quando minimizado if (vidgetContainer.classList.contains('minimized')) { let startX = e.clientX; let startY = e.clientY; let origX = vidgetContainer.offsetLeft; let origY = vidgetContainer.offsetTop; let deltaX = startX - origX; let deltaY = startY - origY; function onMouseMove(e) { isDragging = true; vidgetContainer.style.left = (e.clientX - deltaX) + 'px'; vidgetContainer.style.top = (e.clientY - deltaY) + 'px'; } document.addEventListener('mousemove', onMouseMove); } function onMouseUp(e) { if (vidgetContainer.classList.contains('minimized')) { document.removeEventListener('mousemove', onMouseMove); } document.removeEventListener('mouseup', onMouseUp); let endTime = new Date().getTime(); if (!isDragging || endTime - dragStartTime < 200) { // Foi um clique, não um arrastar if (vidgetContainer.classList.contains('minimized')) { // Se estiver minimizado, expande toggleVidget(globalSupabaseInstance); } else { // Se estiver expandido, alterna entre pausa/reprodução const videoElement = vidgetContainer.querySelector('video'); if (videoElement) { if (videoElement.paused) { videoElement.play(); const playButton = document.getElementById('vidget-play-button'); if (playButton) { playButton.style.display = 'none'; } } else { videoElement.pause(); const playButton = document.getElementById('vidget-play-button'); if (playButton) { playButton.style.display = 'block'; } } } } } isDragging = false; } document.addEventListener('mouseup', onMouseUp); }); } // Anexa os event listeners aos elementos do overlay function attachEventListeners(supabaseInstance, videoSet, videoContainer) { // Close Button const closeButton = document.querySelector('.btn-close.vidget-btn-close'); if (closeButton) { closeButton.addEventListener('click', () => { console.log("Minimizando o widget."); toggleVidget(supabaseInstance); }); } // Copy Link Button const copyLinkButton = document.getElementById('copy-link-button'); if (copyLinkButton) { copyLinkButton.addEventListener('click', (event) => { event.stopPropagation(); const currentUrl = window.location.href; navigator.clipboard.writeText(currentUrl) .then(() => { // Feedback visual temporário const originalBackground = copyLinkButton.style.background; // Adicionar alerta de sucesso alert('Link copiado!'); setTimeout(() => { copyLinkButton.style.background = originalBackground; }, 1000); }) .catch(err => { console.error('Erro ao copiar: ', err); alert('Não foi possível copiar o link. Por favor, tente novamente.'); }); }); } // Share Button const shareButton = document.getElementById('share-button'); const metadata = videoSet[currentVideoIndex].metadata; if (shareButton) { if (metadata.whatsappButton) { shareButton.style.display = 'block'; } else { shareButton.style.display = 'none'; } shareButton.addEventListener('click', () => { toggleSharePopup(); }); } // Navigation Arrows const prevArrow = document.querySelector('.prev-arrow'); const nextArrow = document.querySelector('.next-arrow'); if (videoSet.length > 1) { if (prevArrow) { prevArrow.style.display = ''; prevArrow.addEventListener('click', (event) => { event.stopPropagation(); changeVideo(supabaseInstance, -1); arrowsClicked = true; event.preventDefault(); }); } if (nextArrow) { nextArrow.style.display = ''; nextArrow.addEventListener('click', (event) => { event.stopPropagation(); changeVideo(supabaseInstance, 1); arrowsClicked = true; event.preventDefault(); }); } } else { if (prevArrow) prevArrow.style.display = 'none'; if (nextArrow) nextArrow.style.display = 'none'; } // Sound Buttons const soundOnButton = document.querySelector('#sound-on'); const soundOffButton = document.querySelector('#sound-off'); if (soundOnButton) { soundOnButton.addEventListener('click', () => { if (currentVideoElement) { console.log('Sound On Button Clicked - Current Muted:', currentVideoElement.muted); if (window.innerWidth < VIDGET_CONFIG.MOBILE_BREAKPOINT) { // Para mobile: ao clicar no botão de som desligado, liga o som currentVideoElement.muted = false; updateSoundIcons(false); } else { // Para desktop currentVideoElement.muted = true; updateSoundIcons(true); } } }); } if (soundOffButton) { soundOffButton.addEventListener('click', () => { if (currentVideoElement) { console.log('Sound Off Button Clicked - Current Muted:', currentVideoElement.muted); if (window.innerWidth < VIDGET_CONFIG.MOBILE_BREAKPOINT) { // Para mobile: ao clicar no botão de som ligado, desliga o som currentVideoElement.muted = true; updateSoundIcons(true); } else { // Para desktop currentVideoElement.muted = false; updateSoundIcons(false); } } }); } // Touch Events attachTouchListeners(); // Inicializa o botão de play initPlayButton(videoContainer); } // Anexa os listeners de toque para interações móveis function attachTouchListeners() { // Impede propagação de eventos de botões const buttons = vidgetContainer.querySelectorAll('button:not([data-allow-propagation="false"])'); buttons.forEach(button => { button.addEventListener('touchstart', (e) => { // Apenas pare a propagação para botões específicos e.stopPropagation(); }, { passive: false }); button.addEventListener('touchend', (e) => { e.stopPropagation(); }, { passive: false }); }); // Adicione este bloco para permitir cliques em links dentro do artigo const articleLinks = vidgetContainer.querySelectorAll('article a'); articleLinks.forEach(link => { link.addEventListener('touchstart', (e) => { // Permite a propagação para links e.stopPropagation(); }, { passive: true }); link.addEventListener('touchend', (e) => { e.stopPropagation(); }, { passive: true }); }); // Touch start vidgetContainer.addEventListener('touchstart', (e) => { if (e.target.tagName === 'BUTTON' || e.target.closest('button')) { return; } if (vidgetContainer.classList.contains('minimized')) return; touchStartY = e.touches[0].clientY; lastTouchY = touchStartY; startTime = Date.now(); e.preventDefault(); }, { passive: false }); // Touch move vidgetContainer.addEventListener('touchmove', (e) => { if (vidgetContainer.classList.contains('minimized')) return; lastTouchY = e.touches[0].clientY; e.preventDefault(); }, { passive: false }); // Touch end vidgetContainer.addEventListener('touchend', (e) => { if (vidgetContainer.classList.contains('minimized') || isProcessingSwipe) return; const touchEndY = e.changedTouches[0].clientY; const swipeDistance = touchEndY - touchStartY; const swipeTime = Date.now() - startTime; // Calcula velocidade do swipe const swipeVelocity = Math.abs(swipeDistance) / swipeTime; // Ajusta threshold baseado na velocidade const VELOCITY_THRESHOLD = 0.5; // ajuste conforme necessário if (swipeVelocity > VELOCITY_THRESHOLD || Math.abs(swipeDistance) >= VIDGET_CONFIG.SWIPE_THRESHOLD) { isProcessingSwipe = true; // Determina direção baseado na posição final vs inicial const direction = swipeDistance > 0 ? -1 : 1; // Importante: Remove listeners durante a transição vidgetContainer.style.pointerEvents = 'none'; changeVideo(globalSupabaseInstance, direction); // Reativa após a transição setTimeout(() => { isProcessingSwipe = false; vidgetContainer.style.pointerEvents = 'auto'; }, 500); } }); } // Inicializa o botão de play function initPlayButton(videoContainer) { let existingButton = document.getElementById('vidget-play-button'); if (existingButton) { existingButton.remove(); } let playButton = document.createElement('button'); playButton.id = 'vidget-play-button'; videoContainer.appendChild(playButton); playButton.addEventListener('click', (e) => { e.stopPropagation(); const videoElement = videoContainer.querySelector('video'); if (videoElement && videoElement.paused) { videoElement.play(); playButton.style.display = 'none'; } }); const videoElement = videoContainer.querySelector('video'); if (videoElement) { videoElement.addEventListener('pause', () => { if (!vidgetContainer.classList.contains('minimized')) { playButton.style.display = 'block'; } }); videoElement.addEventListener('play', () => { playButton.style.display = 'none'; }); } } // Carrega e inicia a reprodução de um vídeo async function loadVideo(supabaseInstance, videoSet, videoContainer, index) { if (!videoSet || videoSet.length === 0 || index >= videoSet.length) { console.error('Invalid video set or index out of bounds'); return; } let videoPlayer = videoContainer.querySelector('video') || createVideoPlayer(videoContainer); currentVideoElement = videoPlayer; // IMPORTANTE: Defina muted explicitamente antes de qualquer outra coisa videoPlayer.muted = true; try { await new Promise((resolve, reject) => { // Configurar URL do vídeo videoPlayer.src = `${VIDGET_CONFIG.STORAGE_URL}/${videoSet[index].url}`; // Força atributos críticos para iOS videoPlayer.setAttribute('playsinline', ''); videoPlayer.setAttribute('webkit-playsinline', ''); videoPlayer.setAttribute('x-webkit-airplay', 'allow'); videoPlayer.setAttribute('muted', ''); // Impede problemas de cache em iOS videoPlayer.src = videoPlayer.src + '?nocache=' + Date.now(); // Força um load explícito para limpar quaisquer problemas de cache videoPlayer.load(); videoPlayer.oncanplay = () => { // Atualiza tamanho antes de mostrar if (videoSet && videoSet.length > 0) { updateSizeClass(videoSet[index].metadata || {}); } // MELHORIA: Garanta visibilidade primeiro, depois tente reproduzir vidgetContainer.classList.remove('loading'); vidgetContainer.classList.add('loaded'); vidgetContainer.style.visibility = 'visible'; vidgetContainer.style.opacity = '1'; // Aplica estilos minimizados ANTES de tentar reproduzir if (vidgetContainer.classList.contains('minimized')) { applyMinimizedStyles(); } // Tente múltiplas vezes reproduzir para iOS const playVideo = () => { if (videoPlayer.paused) { const playPromise = videoPlayer.play(); if (playPromise !== undefined) { playPromise.catch(error => { console.warn('Erro de reprodução automática:', error); // Tente novamente em intervalos crescentes setTimeout(playVideo, 50); }); } } }; // Várias tentativas para garantir reprodução playVideo(); setTimeout(playVideo, 100); setTimeout(playVideo, 300); initializeProgressBar(videoPlayer, index); resolve(); }; videoPlayer.onerror = reject; setTimeout(() => reject(new Error('Timeout loading video')), 10000); }); // Setup metrics and events setupVideoEvents(videoPlayer, videoSet, index, supabaseInstance); } catch (error) { console.error('Error loading video:', error); vidgetContainer.classList.remove('loading'); } } // Configura eventos para o elemento de vídeo function setupVideoEvents(videoPlayer, videoSet, index, supabaseInstance) { lastTimeUpdate = 0; // Play event videoPlayer.addEventListener('play', async () => { lastTimeUpdate = videoPlayer.currentTime; await incrementViews(supabaseInstance, videoSet[index].id, index + 1); }); // Timeupdate event videoPlayer.addEventListener('timeupdate', async () => { const currentTime = videoPlayer.currentTime; const duration = videoPlayer.duration; const completionPercentage = (currentTime / duration) * 100; if (currentTime - lastTimeUpdate >= VIDGET_CONFIG.UPDATE_INTERVAL || currentTime === duration) { const timeWatched = currentTime - lastTimeUpdate; await updateWatchTime( supabaseInstance, videoSet[index].id, timeWatched, completionPercentage ); lastTimeUpdate = currentTime; } }); // Pause event videoPlayer.addEventListener('pause', async () => { await saveMetricsOnInterruption(supabaseInstance, videoSet[index].id, lastTimeUpdate); lastTimeUpdate = videoPlayer.currentTime; }); // Abort event videoPlayer.addEventListener('abort', async () => { await saveMetricsOnInterruption(supabaseInstance, videoSet[index].id, lastTimeUpdate); }); // Adicionar evento de clique diretamente no vídeo para pausar/reproduzir // Clique no vídeo para pausar/reproduzir videoPlayer.addEventListener('click', (e) => { e.stopPropagation(); // Verifica se o widget está expandido if (!vidgetContainer.classList.contains('minimized')) { if (videoPlayer.paused) { // Se pausado, inicia reprodução e esconde botão de play videoPlayer.play(); const playButton = document.getElementById('vidget-play-button'); if (playButton) { playButton.style.display = 'none'; } } else { // Se em reprodução, pausa e mostra botão de play videoPlayer.pause(); const playButton = document.getElementById('vidget-play-button'); if (playButton) { playButton.style.display = 'block'; } } } }); // Verifica botão do produto após carregar setTimeout(() => { const productButton = videoPlayer.parentElement.querySelector('[data-vidget-url]'); if (productButton) { productButton.addEventListener('click', async () => { await incrementClicks(supabaseInstance, videoSet[index].id); }); } }, 100); // Inicializar métricas para vídeo videoPlayer.addEventListener('ended', async () => { console.log('Vídeo completado - Atualizando métricas finais'); await updateWatchTime(supabaseInstance, videoSet[index].id, 0, 100, true); }); } // Alterna entre estados minimizado e expandido function toggleVidget(supabaseInstance) { if (toggleLock) return; toggleLock = true; console.log("Alternando estado do vidget"); if (!videoSet.length || typeof currentVideoIndex === 'undefined' || !videoSet[currentVideoIndex]) { console.error("Dados do vídeo não disponíveis."); toggleLock = false; toggleProgressBarVisibility(false); return; } const videoContainer = vidgetContainer.querySelector('.vidget__video-container'); const videoElement = vidgetContainer.querySelector('video'); const articleElement = vidgetContainer.querySelector('article'); const metadata = videoSet[currentVideoIndex].metadata; const expandMode = metadata.expandMode; if (!vidgetContainer.classList.contains('minimized')) { // Se estiver expandido e for minimizar videoElement.pause(); if (typeof lastTimeUpdate !== 'undefined') { saveMetricsOnInterruption(supabaseInstance, videoSet[currentVideoIndex].id, lastTimeUpdate); } minimizeVidget(videoContainer, videoElement, articleElement); toggleProgressBarVisibility(false); if (articleElement) { articleElement.style.marginLeft = "0"; } } else { // Se estiver minimizado e for expandir if (expandMode === 'full') { expandToFullScreen(videoContainer, videoElement, articleElement); } else { expandToFreeMode(videoContainer, videoElement, articleElement); } toggleProgressBarVisibility(true); updateVideoStyles(); if (articleElement) { articleElement.style.marginLeft = videoContainer.style.borderColor ? "0" : "2.5%"; } } setTimeout(() => { toggleLock = false; }, 500); } function setupVideoClickEvent(videoElement) { // Remover qualquer listener existente para evitar duplicação videoElement.removeEventListener('click', videoClickHandler); // Adicionar o novo handler videoElement.addEventListener('click', videoClickHandler); } function videoClickHandler(e) { e.stopPropagation(); // Referência ao elemento de vídeo const videoElement = this; // Referência ao botão de play const playButton = document.getElementById('vidget-play-button'); // Verifica se o widget está expandido if (!vidgetContainer.classList.contains('minimized')) { if (videoElement.paused) { // Se pausado, inicia reprodução e esconde botão de play videoElement.play() .then(() => { if (playButton) { playButton.style.display = 'none'; } }) .catch(error => { console.warn("Erro ao reproduzir vídeo:", error); }); } else { // Se em reprodução, pausa e mostra botão de play videoElement.pause(); if (playButton) { playButton.style.display = 'block'; } } } } // Correção da função expandToFullScreen function expandToFullScreen(videoContainer, videoElement, articleElement) { console.log("Expandindo em modo tela cheia"); document.getElementById('fullScreenOverlay').style.display = 'block'; document.body.style.overflow = 'hidden'; vidgetContainer.style.position = 'fixed'; vidgetContainer.style.top = '50%'; vidgetContainer.style.left = '50%'; vidgetContainer.style.transform = 'translate(-50%, -50%)'; vidgetContainer.style.width = '530px'; vidgetContainer.style.height = '98vh'; videoContainer.style.width = '100%'; const metadata = videoSet[currentVideoIndex].metadata; const shareButton = document.getElementById('share-button'); const copyLinkButton = document.getElementById('copy-link-button'); if (shareButton && metadata.whatsappButton) { shareButton.style.display = 'block'; } if (copyLinkButton) { copyLinkButton.style.display = 'block'; } updateSharePopupStyles(); if (articleElement) { articleElement.style.display = 'block'; articleElement.classList.add('full-mode-article'); articleElement.style.fontSize = '1em'; articleElement.style.height = 'fit-content'; } // set articleElement image to height 'auto' and object fit cover if (articleElement && articleElement.querySelector('img')) { articleElement.querySelector('img').style.height = 'auto'; articleElement.querySelector('img').style.objectFit = 'cover'; } // Esconde o botão de play imediatamente const playButton = document.getElementById('vidget-play-button'); if (playButton) { playButton.style.display = 'none'; } // Garante que o vídeo esteja desmutado videoElement.muted = false; updateSoundIcons(false); // Força a reprodução com uma abordagem mais robusta const forcePlay = () => { // Verificamos se o vídeo já não está em reprodução if (videoElement.paused) { console.log("Tentando iniciar a reprodução do vídeo..."); const playPromise = videoElement.play(); if (playPromise !== undefined) { playPromise .then(() => { console.log("Reprodução iniciada com sucesso!"); // Esconde o botão de play novamente após reprodução confirmada if (playButton) { playButton.style.display = 'none'; } }) .catch(error => { console.warn("Falha ao reproduzir vídeo:", error); // Em caso de falha, tenta novamente após um curto delay setTimeout(forcePlay, 100); }); } } }; // Inicia a tentativa de reprodução imediatamente forcePlay(); // Também programa algumas tentativas adicionais com diferentes intervalos // para aumentar a chance de sucesso com políticas de autoplay restritivas setTimeout(forcePlay, 50); setTimeout(forcePlay, 200); setTimeout(forcePlay, 500); // Configura o evento de clique no vídeo para garantir que o play/pause funcione setupVideoClickEvent(videoElement); vidgetContainer.classList.remove('minimized'); } // Correção da função expandToFreeMode function expandToFreeMode(videoContainer, videoElement, articleElement) { console.log("Expandindo em modo livre"); resetStyles(videoContainer, videoElement, articleElement); videoContainer.style.width = '100%'; videoContainer.style.height = '100%'; const metadata = videoSet[currentVideoIndex].metadata; const overlayPosition = metadata.overlayPosition; applyOverlayStyles(null, overlayPosition); const shareButton = document.getElementById('share-button'); const copyLinkButton = document.getElementById('copy-link-button'); if (shareButton && metadata.whatsappButton) { shareButton.style.display = 'block'; } if (copyLinkButton) { copyLinkButton.style.display = 'block'; } if (window.innerWidth < VIDGET_CONFIG.MOBILE_BREAKPOINT) { vidgetContainer.style.right = '0'; vidgetContainer.style.left = '0'; } if (articleElement) { articleElement.style.display = 'block'; } // Esconde o botão de play imediatamente const playButton = document.getElementById('vidget-play-button'); if (playButton) { playButton.style.display = 'none'; } // Garante que o vídeo esteja desmutado videoElement.muted = false; updateSoundIcons(false); // Método robusto para forçar a reprodução do vídeo const forcePlay = () => { // Verificamos se o vídeo já não está em reprodução if (videoElement.paused) { console.log("Tentando iniciar a reprodução do vídeo em modo livre..."); const playPromise = videoElement.play(); if (playPromise !== undefined) { playPromise .then(() => { console.log("Reprodução iniciada com sucesso!"); // Esconde o botão de play novamente após reprodução confirmada if (playButton) { playButton.style.display = 'none'; } }) .catch(error => { console.warn("Falha ao reproduzir vídeo em modo livre:", error); // Em caso de falha, tenta novamente após um curto delay setTimeout(forcePlay, 100); }); } } }; // Inicia tentativas de reprodução com diferentes intervalos forcePlay(); setTimeout(forcePlay, 50); setTimeout(forcePlay, 200); setTimeout(forcePlay, 500); // Configura o evento de clique no vídeo para garantir que o play/pause funcione setupVideoClickEvent(videoElement); vidgetContainer.classList.remove('minimized'); } // Minimiza o Vidget function minimizeVidget(videoContainer, videoElement, articleElement) { console.log("Minimizing the vidget"); resetStyles(videoContainer, videoElement, articleElement); // Re-apply the overlayPosition to maintain the position when minimized const metadata = videoSet[currentVideoIndex].metadata; const overlayPosition = metadata.overlayPosition; let borderColor = metadata.overlayColor || null; applyOverlayStyles(borderColor, overlayPosition); const shareButton = document.getElementById('share-button'); const copyLinkButton = document.getElementById('copy-link-button'); if (shareButton) { shareButton.style.display = 'none'; } if (copyLinkButton) { copyLinkButton.style.display = 'none'; } vidgetContainer.style.width = '8vw'; vidgetContainer.style.height = '8vw'; videoContainer.style.width = '100%'; videoContainer.style.height = '100%'; vidgetContainer.classList.add('minimized'); } // Reseta os estilos para o estado padrão function resetStyles(videoContainer, videoElement, articleElement) { document.getElementById('fullScreenOverlay').style.display = 'none'; document.body.style.overflow = ''; vidgetContainer.style.position = ''; vidgetContainer.style.top = ''; vidgetContainer.style.left = ''; vidgetContainer.style.transform = ''; vidgetContainer.style.width = ''; vidgetContainer.style.height = ''; videoContainer.style.width = ''; videoContainer.style.height = ''; vidgetContainer.style.right = ''; if (articleElement) { articleElement.style.display = 'none'; articleElement.classList.remove('full-mode-article'); } videoElement.muted = true; } // Atualiza os estilos do popover de compartilhamento function updateSharePopupStyles() { const sharePopup = document.getElementById('share-popup'); if (sharePopup) { const isFullScreen = vidgetContainer.style.width === '530px'; sharePopup.style.maxWidth = isFullScreen ? '100%' : '369px'; sharePopup.style.top = isFullScreen ? '90%' : '80%'; } } function formatWhatsAppNumber(numberWhats) { if (!numberWhats) return ''; // Remove todos os caracteres especiais exceto números let cleanNumber = numberWhats.replace(/\D/g, ''); // Se o número não começar com código do país, adiciona +55 (Brasil) if (!cleanNumber.startsWith('55') && cleanNumber.length <= 11) { cleanNumber = '55' + cleanNumber; } // Se começar com 0, remove (formato antigo do Brasil) if (cleanNumber.startsWith('550')) { cleanNumber = '55' + cleanNumber.substring(3); } return cleanNumber; } // Alterna a exibição do popup de compartilhamento function toggleSharePopup() { // Remove popup existente se houver let existingPopup = document.getElementById('share-popup'); const articleElement = vidgetContainer.querySelector('article'); if (existingPopup) { existingPopup.remove(); // Mostra o article novamente quando fechar o popup if (articleElement && !vidgetContainer.classList.contains('minimized')) { articleElement.style.display = 'block'; } return; } // Esconde o article quando o popup estiver visível if (articleElement) { articleElement.style.display = 'none'; } // Obtém os dados dos metadados do vídeo atual const currentMetadata = videoSet[currentVideoIndex].metadata; const numberWhats = currentMetadata.numberWhats || ""; const msgDataWhats = currentMetadata.msgDataWhats || ""; // Formata o número do WhatsApp (remove caracteres especiais) const formattedNumber = formatWhatsAppNumber(numberWhats); // Obtém a URL atual e cria a mensagem completa const currentUrl = window.location.href; const fullMessage = msgDataWhats ? `${msgDataWhats} ${currentUrl}` : currentUrl; // Cria a URL do WhatsApp com o número e mensagem (usando encodeURI em vez de encodeURIComponent) const whatsappUrl = `https://wa.me/${formattedNumber}?text=${encodeURI(fullMessage)}`; const sharePopup = document.createElement('div'); sharePopup.id = 'share-popup'; const isFullScreen = vidgetContainer.style.width === '530px'; sharePopup.style.cssText = ` width: 95%; max-width: ${isFullScreen ? '100%' : '369px'}; padding: 10px; background: white; border-radius: 20px; position: absolute; top: ${isFullScreen ? '90%' : '90%'}; left: 50%; transform: translate(-50%, -50%); display: flex; flex-direction: column; justify-content: flex-start; align-items: flex-start; gap: 10px; z-index: 10000;`; sharePopup.innerHTML = `
Compartilhar
Enviar no WhatsApp
`; vidgetContainer.appendChild(sharePopup); document.getElementById('whatsapp-share-button').addEventListener('click', () => { window.open(whatsappUrl, '_blank'); }); // Adicione um listener de toque também document.getElementById('whatsapp-share-button').addEventListener('touchend', (e) => { e.preventDefault(); // Previne comportamentos padrão window.open(whatsappUrl, '_blank'); }); document.getElementById('copy-link-button').addEventListener('click', (event) => { event.stopPropagation(); navigator.clipboard.writeText(currentUrl).then(() => { alert('Link copiado para a área de transferência!'); }); event.stopPropagation(); }); } // Atualiza o artigo com os metadados do vídeo atual function updateArticleWithMetadata(videoContainer, metadata, imageUrlFromSet) { // Encontra o artigo dentro do container const article = videoContainer.querySelector('article'); if (!article) return; // Se não há artigo, não faz nada // Compatibilidade: verificar estrutura dos metadados const isNewFormat = 'showTitle' in metadata || 'showDescription' in metadata || 'showPrice' in metadata || 'buttonText' in metadata; // Extrair dados de acordo com o formato const { description, productPrice, price, title, productCardPrice, image_url, buttonUrl, buttonText, showTitle, showDescription, showPrice, showButton, buttonColor } = metadata; // Gerar título baseado no formato dos metadados const displayTitle = isNewFormat ? (showTitle ? title : '') : title; // Gerar descrição baseada no formato dos metadados const displayDescription = isNewFormat ? (showDescription ? description : '') : description; // Gerar preço baseado no formato dos metadados const displayPrice = isNewFormat ? (showPrice ? (price || productPrice || productCardPrice) : '') : (productPrice || productCardPrice); // URL do botão baseada no formato dos metadados const displayButtonUrl = isNewFormat ? (showButton ? (buttonUrl || '') : '') : metadata.productUrl; // Texto do botão baseado no formato dos metadados const displayButtonText = isNewFormat ? (showButton ? (buttonText || 'Ver produto') : '') : 'Ver produto'; // Cor do botão (apenas no novo formato) const buttonStyle = isNewFormat && buttonColor ? `background-color: #${buttonColor}; border-color: #${buttonColor}; color: white;` : ''; // Atualiza o título const titleElement = article.querySelector('[data-vidget-title]'); if (titleElement) { titleElement.textContent = displayTitle || ''; titleElement.style.display = displayTitle ? '' : 'none'; } // Atualiza o preço const priceElement = article.querySelector('[data-vidget-price]'); if (priceElement) { if (displayPrice) { priceElement.textContent = isNaN(displayPrice) ? displayPrice.includes('R') ? displayPrice : `R$ ${displayPrice}` : `R$ ${displayPrice}`; } else { priceElement.style.display = 'none'; } } // Atualiza a descrição const descriptionContainer = article.querySelector('.mb-1'); if (descriptionContainer) { if (displayDescription) { descriptionContainer.innerHTML = VidgetUtils.convertTextToLinks(displayDescription); descriptionContainer.style.display = ''; } else { descriptionContainer.style.display = 'none'; } } // Atualiza o botão const buttonElement = article.querySelector('[data-vidget-url]'); if (buttonElement) { if (displayButtonUrl) { buttonElement.href = displayButtonUrl; buttonElement.textContent = displayButtonText; buttonElement.style.cssText += buttonStyle; buttonElement.style.display = ''; // Adiciona evento de clique para métricas buttonElement.onclick = () => { incrementClicks( globalSupabaseInstance, videoSet[currentVideoIndex].id ); }; } else { buttonElement.style.display = 'none'; } } // Atualiza a imagem se for necessário mostrar const shouldShowImage = isNewFormat ? metadata.showThumbnail : (imageUrlFromSet && (!isNewFormat || metadata.type === 'productCard')); const imgElement = article.querySelector('[data-vidget-img]'); if (imgElement) { const imageUrl = imageUrlFromSet || metadata.image_url; if (shouldShowImage && imageUrl) { imgElement.src = imageUrl; imgElement.style.display = ''; } else { imgElement.style.display = 'none'; } } } // Muda o vídeo atual para o próximo ou anterior function changeVideo(supabaseInstance, direction) { // Previne múltiplas chamadas simultâneas if (isChangingVideo) return; isChangingVideo = true; const videoContainer = document.querySelector('.vidget__video-container'); const currentVideo = videoContainer.querySelector('video'); // Salva métricas do vídeo atual antes de mudar if (currentVideo && currentVideoElement) { saveMetricsOnInterruption( supabaseInstance, videoSet[currentVideoIndex].id, lastTimeUpdate ); } const nextVideoIndex = (currentVideoIndex + direction + videoSet.length) % videoSet.length; const nextVideoUrl = `${VIDGET_CONFIG.STORAGE_URL}/${videoSet[nextVideoIndex].url}`; // Limpa memória do vídeo anterior if (currentVideo) { currentVideo.pause(); currentVideo.removeAttribute('src'); currentVideo.load(); } const nextVideo = document.createElement('video'); // Otimizações específicas para iOS/Safari nextVideo.setAttribute('playsinline', ''); nextVideo.setAttribute('webkit-playsinline', ''); nextVideo.setAttribute('x-webkit-airplay', 'allow'); nextVideo.setAttribute('preload', 'auto'); nextVideo.setAttribute('type', 'video/mp4'); // Configurações básicas nextVideo.loop = true; nextVideo.classList.add('video-item'); nextVideo.autoplay = true; nextVideo.muted = currentVideo ? currentVideo.muted : true; // Estilos otimizados para performance nextVideo.style.cssText = ` position: absolute; top: ${direction > 0 ? 'calc(100% + 4px)' : 'calc(-100% - 4px)'}; left: 0; width: 100%; height: 100%; transition: top 0.2s cubic-bezier(0.4, 0, 0.2, 1); -webkit-transform: translate3d(0,0,0); transform: translate3d(0,0,0); -webkit-backface-visibility: hidden; backface-visibility: hidden; -webkit-perspective: 1000; perspective: 1000; `; // Define src após configurar atributos nextVideo.src = nextVideoUrl; videoContainer.appendChild(nextVideo); currentVideoElement = nextVideo; lastTimeUpdate = 0; // Sistema robusto de garantia de reprodução const maxPlayAttempts = 5; let playAttempts = 0; const ensurePlayback = () => { if (playAttempts >= maxPlayAttempts) return; const playPromise = nextVideo.play(); if (playPromise !== undefined) { playPromise.catch(error => { console.warn('Tentativa de reprodução falhou:', error); playAttempts++; setTimeout(() => ensurePlayback(), 100 * playAttempts); }); } }; // Handlers otimizados nextVideo.addEventListener('loadedmetadata', () => { // Inicializa métricas para o novo vídeo nextVideo.addEventListener('ended', async () => { console.log('Vídeo completado - Atualizando métricas finais'); await updateWatchTime(supabaseInstance, videoSet[nextVideoIndex].id, 0, 100, true); }); ensurePlayback(); }, { once: true }); nextVideo.addEventListener('play', async () => { lastTimeUpdate = nextVideo.currentTime; await incrementViews(supabaseInstance, videoSet[nextVideoIndex].id, nextVideoIndex + 1); }, { once: true }); nextVideo.addEventListener('timeupdate', async () => { const currentTime = nextVideo.currentTime; const duration = nextVideo.duration; const completionPercentage = (currentTime / duration) * 100; if (currentTime - lastTimeUpdate >= VIDGET_CONFIG.UPDATE_INTERVAL || currentTime === duration) { const timeWatched = currentTime - lastTimeUpdate; await updateWatchTime( supabaseInstance, videoSet[nextVideoIndex].id, timeWatched, completionPercentage ); lastTimeUpdate = currentTime; } }); // Atualiza interface nextVideo.muted = currentVideo ? currentVideo.muted : true; updateSoundIcons(nextVideo.muted); // Atualiza o artigo com metadados do novo vídeo const nextVideoMetadata = videoSet[nextVideoIndex].metadata; const nextImageUrl = videoSet[nextVideoIndex].image_url_from_set; // Transição otimizada nextVideo.addEventListener('loadeddata', () => { requestAnimationFrame(() => { nextVideo.style.top = '0'; if (currentVideo) { currentVideo.style.top = direction > 0 ? 'calc(-100% - 4px)' : 'calc(100% + 4px)'; currentVideo.style.transition = 'top 0.2s cubic-bezier(0.4, 0, 0.2, 1)'; } // Força a reprodução imediata para iOS const playVideo = () => { const playPromise = nextVideo.play(); if (playPromise !== undefined) { playPromise.catch(() => { // Se falhar, tenta novamente imediatamente setTimeout(playVideo, 50); }); } }; playVideo(); // Atualiza metadados no artigo updateArticleWithMetadata(videoContainer, nextVideoMetadata, nextImageUrl); }); setTimeout(() => { if (currentVideo) { currentVideo.remove(); } initializeProgressBar(nextVideo, nextVideoIndex); // Garante novamente a reprodução nextVideo.play().catch(() => { setTimeout(() => nextVideo.play(), 50); }); isChangingVideo = false; }, 300); }, { once: true }); // Atualiza barras de progresso const progressBars = document.querySelectorAll('.vidget__progress-bar'); progressBars.forEach((bar, index) => { if (index < nextVideoIndex) { bar.classList.add('completed'); bar.style.width = '100%'; } else if (index > nextVideoIndex) { bar.classList.remove('completed', 'active'); bar.style.width = '0%'; } }); // Escalonamento de tentativas de reprodução [50, 100, 200].forEach(delay => { setTimeout(ensurePlayback, delay); }); currentVideoIndex = nextVideoIndex; } // Esconde o vídeo e reexibe após o tempo definido async function hideVideoOverlay() { let videoPlayer = vidgetContainer.querySelector('video'); if (videoPlayer) { await saveMetricsOnInterruption( globalSupabaseInstance, videoSet[currentVideoIndex].id, lastTimeUpdate ); videoPlayer.pause(); videoPlayer.src = ''; console.log("Vídeo pausado e fonte removida."); } else { console.log("Nenhum vídeo encontrado para pausar."); } vidgetContainer.style.display = 'none'; console.log("Widget ocultado."); // Define o temporizador para reexibir o widget após o tempo configurado setTimeout(() => { console.log("Temporizador concluído, tentando mostrar o widget novamente."); showVideoOverlay(); }, VIDGET_CONFIG.HIDE_TIMEOUT); } // Mostra o vídeo function showVideoOverlay() { let videoPlayer = vidgetContainer.querySelector('video'); if (videoPlayer && videoPlayer.src) { vidgetContainer.style.display = 'block'; videoPlayer.play(); console.log("Widget mostrado e vídeo reproduzido."); } else { console.log("Nenhum vídeo disponível para carregar ou vídeo sem fonte."); } } // Incrementa visualizações para um vídeo async function incrementViews(supabaseInstance, videoId, videoIndex) { try { const { data, error: fetchError } = await supabaseInstance .from('vidget::videos') .select('metadata') .eq('id', videoId) .single(); if (fetchError) throw fetchError; // Inicializa métricas se necessário if (!data.metadata.metrics) { data.metadata.metrics = {}; } const currentVideoId = `video_${videoIndex}`; if (!data.metadata.metrics[currentVideoId]) { data.metadata.metrics[currentVideoId] = { duration: currentVideoElement.duration, totalViews: 0, totalClicks: 0, totalWatchTime: 0, averageWatchTime: 0, dailyMetrics: {}, completionRates: { "0-25": 0, "26-50": 0, "51-75": 0, "76-100": 0 } }; } const currentDate = new Date().toISOString().split('T')[0]; const metrics = data.metadata.metrics[currentVideoId]; // Incrementa visualizações metrics.totalViews++; if (!metrics.dailyMetrics[currentDate]) { metrics.dailyMetrics[currentDate] = { views: 0, clicks: 0, watchTime: 0, sessions: 0 }; } metrics.dailyMetrics[currentDate].views++; const { error: updateError } = await supabaseInstance .from('vidget::videos') .update({ metadata: data.metadata }) .eq('id', videoId); if (updateError) throw updateError; } catch (error) { console.error('Erro ao incrementar visualizações:', error); } } // Incrementa cliques para um vídeo async function incrementClicks(supabaseInstance, videoId, videoIndex) { try { // Se videoIndex não for fornecido, usa o índice atual if (videoIndex === undefined) { videoIndex = currentVideoIndex + 1; } const { data, error: fetchError } = await supabaseInstance .from('vidget::videos') .select('metadata') .eq('id', videoId) .single(); if (fetchError) throw fetchError; const currentVideoId = `video_${videoIndex}`; if (!data.metadata.metrics?.[currentVideoId]) { data.metadata.metrics = { ...data.metadata.metrics, [currentVideoId]: { duration: currentVideoElement.duration, totalViews: 0, totalClicks: 0, totalWatchTime: 0, averageWatchTime: 0, dailyMetrics: {}, completionRates: { "0-25": 0, "26-50": 0, "51-75": 0, "76-100": 0 } } }; } const currentDate = new Date().toISOString().split('T')[0]; const metrics = data.metadata.metrics[currentVideoId]; // Incrementa clicks metrics.totalClicks++; if (!metrics.dailyMetrics[currentDate]) { metrics.dailyMetrics[currentDate] = { views: 0, clicks: 0, watchTime: 0, sessions: 0 }; } metrics.dailyMetrics[currentDate].clicks++; const { error: updateError } = await supabaseInstance .from('vidget::videos') .update({ metadata: data.metadata }) .eq('id', videoId); if (updateError) throw updateError; } catch (error) { console.error('Erro ao incrementar cliques:', error); } } // Atualiza o tempo de visualização async function updateWatchTime(supabaseInstance, videoId, timeWatched, completionPercentage, isVideoComplete = false) { try { console.log('Atualizando métricas - Tempo assistido:', timeWatched, 'Porcentagem:', completionPercentage); const { data, error: fetchError } = await supabaseInstance .from('vidget::videos') .select('metadata') .eq('id', videoId) .single(); if (fetchError) throw fetchError; if (!data.metadata.metrics) { data.metadata.metrics = {}; } const currentVideoId = `video_${currentVideoIndex + 1}`; const currentDate = new Date().toISOString().split('T')[0]; // Inicializa a estrutura de métricas se necessário if (!data.metadata.metrics[currentVideoId]) { data.metadata.metrics[currentVideoId] = { duration: currentVideoElement.duration, totalViews: 0, totalClicks: 0, totalWatchTime: 0, averageWatchTime: 0, dailyMetrics: { [currentDate]: { views: 0, clicks: 0, watchTime: 0, sessions: 1 } }, completionRates: { "0-25": 0, "26-50": 0, "51-75": 0, "76-100": 0 } }; } const metrics = data.metadata.metrics[currentVideoId]; // Atualiza tempo total assistido metrics.totalWatchTime = (metrics.totalWatchTime || 0) + timeWatched; // Atualiza métricas diárias if (!metrics.dailyMetrics[currentDate]) { metrics.dailyMetrics[currentDate] = { views: 0, clicks: 0, watchTime: 0, sessions: 1 }; } metrics.dailyMetrics[currentDate].watchTime += timeWatched; // Atualiza média apenas se houver visualizações if (metrics.totalViews > 0) { metrics.averageWatchTime = metrics.totalWatchTime / metrics.totalViews; } // Atualização dos completion rates if (isVideoComplete) { // Se o vídeo foi completado, incrementa apenas o bucket 76-100 metrics.completionRates["76-100"]++; console.log('Vídeo completado - Incrementando bucket 76-100'); } else { // Se não completou, incrementa apenas o bucket correspondente ao progresso atual let currentBucket = VidgetUtils.getCompletionBucket(completionPercentage); if (currentBucket) { metrics.completionRates[currentBucket]++; console.log(`Visualização parcial - Incrementando bucket ${currentBucket}`); } } // Armazena a última porcentagem máxima atingida metrics._lastMaxCompletion = completionPercentage; console.log('Métricas atualizadas:', { completionRates: metrics.completionRates, totalWatchTime: metrics.totalWatchTime, dailyWatchTime: metrics.dailyMetrics[currentDate].watchTime }); const { error: updateError } = await supabaseInstance .from('vidget::videos') .update({ metadata: data.metadata }) .eq('id', videoId); if (updateError) throw updateError; } catch (error) { console.error('Erro ao atualizar tempo de visualização:', error); } } // Salva métricas quando há interrupção na visualização async function saveMetricsOnInterruption(supabaseInstance, videoId, lastTimeUpdate) { if (!currentVideoElement) return; const currentTime = currentVideoElement.currentTime; const timeWatched = currentTime - lastTimeUpdate; const completionPercentage = (currentTime / currentVideoElement.duration) * 100; if (timeWatched > 0) { console.log('Salvando métricas na interrupção - Progresso:', completionPercentage.toFixed(2) + '%'); await updateWatchTime( supabaseInstance, videoId, timeWatched, completionPercentage, false ); } } // Inicializa o Vidget if (document.readyState === 'loading') { // Se ainda estiver carregando, adicione um listener document.addEventListener('DOMContentLoaded', function () { initVidget(); }); } else { // Se já estiver carregado, execute imediatamente initVidget(); }